﻿module internal FODATAC.Reflection

open System.Reflection
open System
open System.Runtime.Serialization
open System.IO
let a = typeof<BodyParameter>.GetTypeInfo().Assembly
let fillListMethodInfo = 
    let modu = a.GetType("FODATAC.Query")
    modu.GetRuntimeMethods() |>Seq.find (fun c -> c.Name ="fillList")

let createAndFillInstanceMethodInfo = 
    let modu = a.GetType("FODATAC.Query")
    modu.GetRuntimeMethods() |>Seq.find (fun c -> c.Name ="createAndFillInstance")
let getListMethodInfo = 
    let modu = a.GetType("FODATAC.Query")
    modu.GetRuntimeMethods() |>Seq.find (fun c -> c.Name ="getList")
let readeResponseMethodInfo = 
    let modu = a.GetType("FODATAC.Query")
    modu.GetRuntimeMethods() |>Seq.find (fun c -> c.Name ="readResponseGeneric")

let getGenericCollectionType (ttype : Type) = 
    if not ttype.IsConstructedGenericType then None
    else 
        let genericTypeDef = ttype.GetGenericTypeDefinition()
        match genericTypeDef.Name with
        | "ICollection`1" | "IList`1" | "List`1" -> Some(typedefof<System.Collections.Generic.List<_>>)
        | "ISet`1" -> Some(typedefof<System.Collections.Generic.HashSet<_>>)
        | _ -> None

/// <summary>
/// Creates Instance even if no public ctor available
/// </summary>
let createInstance (ttype : Type) = 
    let constructors = ttype.GetTypeInfo().DeclaredConstructors |> Seq.toArray
    match (constructors) with
    | [||] -> Activator.CreateInstance(ttype)
    | _ -> 
        let targetCtr = constructors |> Seq.find (fun cc -> cc.GetParameters().Length = 0)
        targetCtr.Invoke([||])

//finds setter for a field or method that is decorated with DataMember attribute and Name matches
let rec findSetter (ttype : Type, name : String) = 
    let mem = 
        let fields = ttype.GetRuntimeFields() |> Seq.map(fun c -> c :> MemberInfo )
        let props =  ttype.GetRuntimeProperties() |> Seq.map(fun c -> c :> MemberInfo )
        let members = Seq.concat([| fields;  props|])
        members 
        |> Seq.tryFind (fun mem -> 
               let someAtt = 
                   mem.GetCustomAttributes(typeof<DataMemberAttribute>, true)
                   |> Seq.choose (fun p -> 
                          match p with
                          | :? DataMemberAttribute as x -> Some(x)
                          | _ -> None)
                   |> Seq.tryPick Some
               match someAtt with
               | Some(att) -> (att.Name = name || mem.Name = name)
               | _ -> false)
    match mem with
    | Some(:? PropertyInfo as pi) when pi.CanWrite-> Some((Setter.Property pi), pi.PropertyType)
    | Some(:? FieldInfo as fi) -> Some((Setter.Field fi), fi.FieldType)
    | _  when ttype.GetTypeInfo().BaseType = typeof<obj>-> None
    | _ -> findSetter( (ttype.GetTypeInfo().BaseType), name)

let inline getMemberName (memberr : ^TMember) = 
    let att = 
        ((((((((((((^TMember : (member GetCustomAttributes : Type -> bool -> obj []) (memberr, typeof<DataMemberAttribute>, true))))))))))))).[0] :?> DataMemberAttribute
    if String.IsNullOrWhiteSpace att.Name then ((((((((((((^TMember : (member Name : String) memberr))))))))))))
    else att.Name

let setSetterValue setter instance value = 
    match setter with
    | Field(fi) -> fi.SetValue(instance, value)
    | Property(pi) -> pi.SetValue(instance, value, null)

let getValueAndMemberName (setter:Setter) (instance:obj) = 
    match setter with
    | Field(fi) -> 
        let memberName = 
            let att = fi.GetCustomAttribute(typeof<DataMemberAttribute>, true) :?> DataMemberAttribute
            if String.IsNullOrWhiteSpace att.Name then fi.Name
            else att.Name
        fi.GetValue(instance), memberName, fi.FieldType
    | Property(pi) -> 
        let memberName = 
            let att = pi.GetCustomAttribute(typeof<DataMemberAttribute>, true) :?> DataMemberAttribute
            if String.IsNullOrWhiteSpace att.Name then pi.Name
            else att.Name    
        pi.GetValue(instance, null), memberName, pi.PropertyType

let getPropertyInfo (entityType : Type) name = 
    entityType.GetRuntimeProperties() |> Seq.find (fun c-> c.Name = name) //(name, BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.FlattenHierarchy)

let isStream (entityType : Type) name = 
    let prop = getPropertyInfo entityType name
    typeof<Stream>.GetTypeInfo().IsAssignableFrom ((prop.PropertyType).GetTypeInfo())
